package whf.framework.web.tag;

import java.io.IOException;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import javax.servlet.jsp.JspException;

import whf.framework.datasource.DataRow;
import whf.framework.datasource.impl.SingleDataSource;
import whf.framework.entity.AbstractEntity;
import whf.framework.entity.Entity;
import whf.framework.entity.PropertyTypeEnum;
import whf.framework.ext.DictManagerImp;
import whf.framework.ext.entity.DictItem;
import whf.framework.log.Log;
import whf.framework.log.LogFactory;
import whf.framework.meta.Meta;
import whf.framework.meta.MetaFactory;
import whf.framework.meta.entity.IMeta;
import whf.framework.meta.entity.IProperty;
import whf.framework.meta.entity.View;
import whf.framework.meta.service.ObjectServiceImp;
import whf.framework.meta.service.ViewServiceImp;
import whf.framework.security.UserContext;
import whf.framework.service.Service;
import whf.framework.util.AttributeStringBuilder;
import whf.framework.util.BeanFactory;
import whf.framework.util.BeanUtils;
import whf.framework.util.ParamEncoder;
import whf.framework.util.StringUtils;
import whf.framework.util.Utils;
import whf.framework.web.Constants;
import whf.framework.web.WebContextHolder;
import whf.framework.web.query.Field;
import whf.framework.web.tag.ext.TableAttributes;
import whf.framework.web.tag.ext.TableTagConstants;

/**
 * 用作查询表单
 * @author wanghaifeng
 * @create Jan 21, 2007 12:00:07 PM
 * 
 */
@SuppressWarnings(value={"unchecked","serial"})
public class QueryFormTag extends HtmlTag implements TableTagConstants{
	private static Log log = LogFactory.getLog(QueryFormTag.class);

	// ----------------------条件------------------------------
	/**
	 * 用于查找meta，
	 */
	private String boClassName;
	
	private String namePrefix = "";
	
	/**
	 * 显示列数，对于displayStyle=form时有效
	 */
	private int displayColumnCount;
	
	/**
	 * 使用不同的后缀将可以获取不同的css class
	 * @property String:cssClassSuffix
	 */
	private String cssClassSuffix;
	
	private int visibleLevel;
	
	/**
	 * property:meta
	 * maybe a whf.framework.meta.entity.Object or whf.framework.meta.entity.View
	 */
	private IMeta boMeta;
	
	/**
	 * 视图代码
	 * @property String:view
	 */
	private String view;
	
	private ParamEncoder paramEncoder;
	
	private String displayCellWidth = "";
	
	/**
	 * if you use multi this tag in a jsp page, so this required
	 * system can distinguish each
	 */
	private String uid;
	
	private TableModel tableModel;

	private String caption;
	/**
	 * @param boClassName The boClassName to set.
	 */
	public void setBoClassName(String boClassName) {
		this.boClassName = boClassName;
	}

	public void setNamePrefix(String namePrefix) {
		this.namePrefix = namePrefix;
	}

	public void setCaption(String caption) {
		this.caption = caption;
	}
	/**
	 * @param cssClassSuffix The cssClassSuffix to set.
	 */
	public void setCssClassSuffix(String cssClassSuffix) {
		this.cssClassSuffix = cssClassSuffix;
	}

	/**
	 * @param displayColumnCount The displayColumnCount to set.
	 */
	public void setDisplayColumnCount(int displayColumnCount) {
		this.displayColumnCount = displayColumnCount;
	}

	/**
	 * @param uid The uid to set.
	 */
	public void setUid(String uid) {
		this.uid = uid;
	}

	/**
	 * @param view The view to set.
	 */
	public void setView(String view) {
		this.view = view;
	}
	
	public void setVisibleLevel(int level){
		this.visibleLevel = level;
	}
	
	public void setDisplayCellWidth(String displayCellWidth){
		this.displayCellWidth = displayCellWidth;
	}
	//-----------------------------------------------------------------------------------------
	private String getSessionUniqueID(){
		String pageUri = (String)request().getAttribute(Constants.STRUTS_NAVIGATION_PATH);
		if(StringUtils.isEmpty(pageUri)){
			pageUri = request().getRequestURI()+"?";
		}
		pageUri = "Query:"+pageUri;
		return pageUri;
	}
	
	private void initColumnsFromMeta(){
		if(this.boMeta != null){
			List<IProperty> properties = this.boMeta.getSortedProperties();
			for(IProperty prop: properties){
				if(prop.isVisible() && this.visibleLevel >= prop.getVisibleLevel() && prop.isQueryCondition()){
					this.tableModel.addColumn(new TableColumn(prop));
				}
			}
		}
	}
	
	private void initColumns() throws JspException{
		UserContext uc = WebContextHolder.getWebContext().getUserContext();
		List<TableColumn> cols = null;
		if(uc != null) {
			TableAttributes tableAttributes = uc.getTableAttributes(this.getSessionUniqueID());
			if(tableAttributes != null){
				cols = tableAttributes.getTableColumns();
			}
		}
		if(cols != null){
			this.tableModel.setColumns(cols);
		} else {
			if(this.tableModel.getColumns().isEmpty()){
				this.initColumnsFromMeta();
			}
		}
	}
	
	private whf.framework.meta.entity.View getView(){
		try{
			List list = ViewServiceImp.getViewService().findWithoutLazy("t.code=?",this.view);
			if(list.size() > 0){
				return (whf.framework.meta.entity.View)list.get(0);
			} else {
				return null;
			}
		} catch(Exception e){
			log.debug(this, e);
			return null;
		}
	}
	
	private whf.framework.meta.entity.IMeta getMeta(){
		try{
			//在request中自动匹配
			Object viewObject = request().getAttribute(Constants.AUTO_SETTER_VIEW);
			if(viewObject != null){
				if(viewObject instanceof whf.framework.meta.entity.View){
					return (whf.framework.meta.entity.View)viewObject;
				} else if(viewObject instanceof String){
					whf.framework.meta.entity.View view = (whf.framework.meta.entity.View)ViewServiceImp.getViewService().findByCode((String)viewObject);
					if(view != null){
						return view;
					}
				}
			}
			//使用类所对应的基本meta
			whf.framework.meta.entity.Object result = ObjectServiceImp.getObjectService().findByEntityClass(this.boClassName);
			return result;
		}catch(Exception e){
			log.info(this, e);
			return null;
		}
	}
	
	private void initParameters(boolean fetchAll) throws JspException {
		if(StringUtils.isEmpty(this.boClassName)){
			Meta meta = (Meta)request().getAttribute(Constants.STRUTS_META);
			if(meta != null ){
				this.boClassName = meta.getModelClass().getName();
			}
		}
		//初始化Meta元数据
		if(this.boMeta == null && !StringUtils.isEmpty(this.view)){
			this.boMeta = this.getView();
			if(this.boMeta != null){
				this.boClassName = ((View)this.boMeta).getReferencedObject().getBoClassName();
			}
		}
		if(this.boMeta == null){
			this.boMeta = this.getMeta();
		}
	}
	
	private String getUid() {
		if(StringUtils.isEmpty(this.uid)){
			this.uid = this.boClassName;
		}
		if (this.paramEncoder == null) {
			this.paramEncoder = new ParamEncoder(uid);
		}
		return this.paramEncoder.getParameterIdentifier();
	}
	
	public int doStartTag() throws JspException {
		this.initParameters(false);
		Entity bo = null;
		if(!StringUtils.isEmpty(this.boClassName)){
			try{
				bo = (Entity)(Utils.getClassByClassName(this.boClassName)).newInstance();
			} catch(Exception e){
				log.error(this, e);
			}
		}
		if(bo == null){
			bo = new Obj();
		}
		this.tableModel = new TableModel(new SingleDataSource<DataRow>(bo), this.boMeta);
		this.tableModel.setId(getUid());
		this.tableModel.addRow(new TableRow(bo, 1, null));
		return 2;
	}
	
	private String cssClass(String cssClass){
		if(this.cssClassSuffix == null){
			return cssClass;
		} else {
			return cssClass + this.cssClassSuffix;
		}
	}
	
	public int doEndTag() throws JspException {
		int returnValue = EVAL_PAGE;
		try{
			this.initColumns();
			if(this.tableModel.getColumns().size() > 0) {
				this.outputCaption();
				super.setClazz(this.cssClass(CSS_FORM_CLASS));
				this.writeFormHtml();
				//
				super.printInput("hidden", Constants.QUERY_FORM_TAG, "true");
				super.printInput("hidden", Constants.BO_CLASS_NAME, this.boClassName);
				super.printInput("hidden", this.boClassName+Constants.NAME_PREFIX, this.namePrefix);				//名称前缀
				super.printInput("hidden", this.boClassName+Constants.META, String.valueOf(this.boMeta == null?0:this.boMeta.getId()));	//保存类的元数据供后期合法性校验
				if(this.boMeta != null && this.boMeta instanceof View){
					super.printInput("hidden", this.boClassName+Constants.META_TYPE, "View");
					super.printInput("hidden", this.boClassName+Constants.META_VIEW, ((View)boMeta).getCode());
				} else {
					super.printInput("hidden", this.boClassName+Constants.META_TYPE, "Object");
				}
			}
			//
		}catch(IOException e){
			log.error(this,e);
			throw new JspException(e);
		}
		release();
		return returnValue;
	}
	
	private void outputCaption() throws JspException {
		if(StringUtils.equalsIgnoreCase(this.caption, "none") || StringUtils.equalsIgnoreCase(this.caption, "hide")) return;
		if(StringUtils.isEmpty(this.caption) && this.boMeta != null) this.caption = this.boMeta.getName();
		if(StringUtils.isEmpty(this.caption)) return;
		println("<table width='100%' border='0' align='center' cellpadding='0' cellspacing='0' class='whf_object_title' valign='middle'><tr height='25px'><td>"+ this.caption +"</td></tr></table>");
	}
	
	private void writeFormHtml() throws JspException, IOException{
		int count = 0;
		Map<String, String> formLabelClass = Utils.newHashMap();	//表单标题显示风格
		formLabelClass.put("class",this.cssClass(CSS_FORM_LABEL_CLASS));
		if(this.displayColumnCount == 0) this.displayColumnCount = 1;
		if(StringUtils.isEmpty(this.displayCellWidth)){
			String displayTitleWidth = String.valueOf(150 / (this.displayColumnCount * 5)) + "%";
			this.displayCellWidth = String.valueOf(350 / (this.displayColumnCount * 5)) + "%";
			formLabelClass.put("width", displayTitleWidth);
		}
		for (Iterator<TableRow> rows = this.tableModel.getRows().iterator(); rows.hasNext();) {
			count ++;
			println(super.getOpenTag("table"));
			println("<tbody>");
			TableRow row = rows.next();
			//
			for (Iterator<TableColumn> columnIterator = this.tableModel.getColumns().iterator(); columnIterator.hasNext();) {
				println("<tr class='"+this.cssClass(CSS_FORM_ROW_CLASS)+"'>");
				TableColumn col = columnIterator.next();
				int colspan = col.getColspan();
				if(colspan < 1) colspan = 1;
				String requiredText = "";
				String displayValue = this.getCellContent(row, col);
				formLabelClass.put("id","title_"+col.getProperty()); //使用javascript验证时可以找到property对应的title
				super.printTd(col.getDisplayTitle()+requiredText, formLabelClass);
				print("<td width='"+this.displayCellWidth+"' class='"+this.cssClass(CSS_FORM_CONTENT_CLASS)+"' colspan='"+colspan+"'>").print(displayValue).print("</td>");
				//
				int columnCount = colspan;
				while(columnCount < this.displayColumnCount){
					if(columnIterator.hasNext()){
						col = columnIterator.next();
						colspan = col.getColspan();
						if(colspan > this.displayColumnCount - columnCount ){
							colspan = this.displayColumnCount - columnCount;
						}
						if(colspan <= 1) colspan = 1;
						displayValue = this.getCellContent(row, col);
						formLabelClass.put("id","title_"+col.getProperty());
						super.printTd(col.getDisplayTitle()+requiredText, formLabelClass);
						print("<td width='"+this.displayCellWidth+"' class='"+this.cssClass(CSS_FORM_CONTENT_CLASS)+"'>").print(displayValue).print("</td>");
						columnCount += colspan;
					} else {
						super.printTd("&nbsp;", formLabelClass);
						print("<td width='"+this.displayCellWidth+"' class='"+this.cssClass(CSS_FORM_CONTENT_CLASS)+"'>").print("&nbsp;").print("</td>");
						columnCount++;
					}
				}
				//
				println("</tr>");
			}
			println("<tr class='"+this.cssClass(CSS_FORM_ROW_CLASS)+"'><td colspan='" + (2 * this.displayColumnCount ) + " class='"+this.cssClass(CSS_FORM_CONTENT_CLASS)+"'>");
			println("<input type='submit' value=' 查询 '>").println("</td></tr>");
			println("</tbody>");
			println("</table>");
		}
	}
	
	private List<Entity> enumSelections(Class dataTypeClass,String queryString, DataRow rowObject){
		try{
			Service service = BeanFactory.getService(MetaFactory.findByModelClass(dataTypeClass));
			queryString = BeanUtils.replaceByProperty(rowObject, queryString);		//使用对象属性值替换查询字符串中的值
			return service.find(queryString);
		}catch(Exception e){
			log.warn(this, e);
			return null;
		}
	}
	
	private String getCellContent(TableRow row, TableColumn col){
		DataRow rowObject = row.getRowObject();
		Object propertyValue = row.getPropertyValue(col.getProperty());
		String displayValue = this.getEditableCell(col, propertyValue, rowObject);
		return displayValue;
	}
	
	private String[] getQueryFieldValues(String fieldName) {
		UserContext userContext = WebContextHolder.getWebContext(request()).getUserContext();
		String queryId = "";
		if(this.boMeta instanceof View) {
			queryId = "VIEW:" + this.view;
		} else {
			queryId = "OBJECT:" + this.boMeta.getId();
		}
		List<Field> fields = userContext.getQueryFieldValues(queryId);
		if(fields == null) return null;
		for(Field field: fields) {
			if(StringUtils.equals(field.getName(), fieldName)) {
				return field.getValues();
			}
		}
		return null;
	}

	private String getEditableCell(TableColumn col, Object propertyValue, DataRow rowObject){
		String name = this.namePrefix+col.getProperty();
		String id = name;
		String dataType = col.getDataType();
		if(StringUtils.isEmpty(dataType))  dataType = "String";
		String queryString = BeanUtils.replaceByProperty(rowObject, col.getQueryString());
		
		//判定业务对象类型
		PropertyTypeEnum propertyTypeInfo = col.getPropType(propertyValue);
		Class dataTypeClass = Utils.getClassByClassName(dataType);
		String[] values = this.getQueryFieldValues(col.getProperty());
		//
		AttributeStringBuilder res = new AttributeStringBuilder(false);
		if(propertyTypeInfo == PropertyTypeEnum.ENTITY){
			propertyValue = values != null && values.length > 0? values[0]: null;
			name = name+Constants.BOID_SUFFIX;
			Entity entity = null;
			if(propertyValue != null){
				if(propertyValue instanceof Entity){
					entity = (Entity)propertyValue;
				} else { //字符串绑定对象
					try{
						long valId = Long.parseLong(propertyValue.toString());
						Meta meta = MetaFactory.findByModelClass(dataTypeClass);
						Service service = BeanFactory.getService(meta);
						entity = service.findByPrimaryKey(valId);
					}catch(Exception ignore){
						//
					}
				}
			}
			if(col.isEnumSelections()){//枚举选项
				long selectionId = entity == null?0:entity.getId();
				List<Entity> list = enumSelections(dataTypeClass, queryString, rowObject);
				if(list != null){
					for(Entity b:list){
						String checked = "";
						if(values != null)
							for(String v: values) {
								if(StringUtils.equals(v, String.valueOf(selectionId))) {
									checked = " checked ";
								}
							}
						res.append("<span nowrap>");
						res.append("<input type='checkbox'").append("name", name).append("id", id).append("value", b.getId()).append(checked).append(">");
						res.append(b.getName());
						res.append("</span>");
					}
				}
			} else {
				String dispValue = entity != null? entity.getDisplayName(): "";
				res.append("<span nowrap>");
				res.append("<input type='text' class='selectInput' ").append("value", dispValue);
				res.append("name","_"+name).append("id","_"+id).append("dataType", dataType).append("kind", dataType);
				res.append("queryString", queryString).append("required", col.isRequired());
				res.append("dictCategory", col.getDictCategoryCode());
				res.append("multiselect",true).append("size", col.getDisplayWidth());
				if(!StringUtils.isEmpty(col.getPromptTitle())) res.append("title", col.getPromptTitle());
				if(!StringUtils.isEmpty(col.getHtmlAttributes())) res.append(col.getHtmlAttributes());
				res.append(">");
				
				if(col.isQueryCondition() && col.getPropertyObject() != null){ //使用搜索按钮
					res.append("<img ").append("style","cursor:hand").append("src",request().getContextPath()+"/images/search.gif").append("onclick","openSearchWindow('"+id+"','"+col.getPropertyObject().getId()+"','false')").append(">");
				} else {
					res.append("<img ").append("src",request().getContextPath()+"/images/search.gif").append(">");
				}
				res.append("</span>");
				
				res.append("<input type='hidden' ");
				res.append("name", name).append("id", id).append("value", (entity == null? 0: entity.getId()));
				res.append(">");
			}
		} else if(propertyTypeInfo == PropertyTypeEnum.DICT){//对于字典项，除了显示与BO区别，其它一样
			propertyValue = values != null && values.length > 0?values[0]:"";
			String dispValue = values != null && values.length > 0? values[0]: "";
			if(propertyValue != null && !StringUtils.isEmpty(propertyValue.toString())){
				DictItem dictItem = DictManagerImp.getDictManager().findDictItem(col.getDictCategoryCode(), propertyValue == null?"": propertyValue.toString());
				dispValue = dictItem == null? "": dictItem.getName();
			}
			dataType = DictItem.class.getName();
			res.append("<span nowrap>");
			res.append("<input type='text' class='selectInput' ").append("value", dispValue);
			res.append("name","_"+name).append("id","_"+id).append("dataType", dataType).append("maxLength", "").append("kind", dataType);
			res.append("queryString", queryString).append("required", col.isRequired());
			res.append("dictCategory", col.getDictCategoryCode());
			res.append("multiselect",true).append("size", col.getDisplayWidth());
			if(!StringUtils.isEmpty(col.getPromptTitle())) res.append("title", col.getPromptTitle());
			res.append(">");
			
			res.append("<img ").append("src",request().getContextPath()+"/images/select.gif").append(">").append("</span>");
			
			res.append("<input type='hidden' ");
			res.append("name", name).append("id", id).append("value", propertyValue == null?"":propertyValue.toString());
			res.append(">");
		} else if(propertyTypeInfo == PropertyTypeEnum.BOOLEAN){
			String dispValue = values != null && values.length > 0?values[0]:"";
			if(StringUtils.equalsIgnoreCase(dispValue, "&nbsp;")){
				dispValue = "";
			}
			res.append("<span class='booleanInput'").append("title",col.getPromptTitle()).append("nowrap>");
			res.append("<input type='radio' ");
			res.append("name", name).append("id", id+"_T").append("value","1").append("1".equals(dispValue)?"checked":"");
			res.append(">").append(BOOLEAN_TRUE);
			res.append("&nbsp;&nbsp;");
			res.append("<input type='radio' ");
			res.append("name", name).append("id", id+"_F").append("value","0").append("0".equals(dispValue)?"":"checked");
			res.append(">").append(BOOLEAN_FALSE);
			res.append("&nbsp;&nbsp;");
			res.append("<input type='radio' ");
			res.append("name", name).append("id", id+"_F").append("value","").append(StringUtils.isEmpty(dispValue)?"":"checked");
			res.append(">").append(BOOLEAN_ALL);
			res.append("</span>");
		} else if(propertyTypeInfo == PropertyTypeEnum.DATETIME){
			String dispValue = values != null && values.length > 0?values[0]:"";
			if(StringUtils.equalsIgnoreCase(dispValue, "&nbsp;")){
				dispValue = "";
			}
			res.append("<span nowrap>");
			res.append("<input type='text' class='datetime' ").append("datatype", dataType);
			res.append("name", name + "_1").append("id", id + "_1").append("value", dispValue);
			if(!StringUtils.isEmpty(col.getPromptTitle())) res.append("title", col.getPromptTitle());
			res.append(">");
			res.append("<img ").append("src",request().getContextPath()+"/images/datetime.gif").append("style", "cursor:hand").append("onclick","showDatetimeWindow('"+id+"_1','"+col.getDataType()+"');return false").append(">");
			res.append("</span>");
			
			res.append(" ~ ");
			dispValue = values != null && values.length > 1?values[1]:"";
			res.append("<span nowrap>");
			res.append("<input type='text' class='datetime' ").append("datatype", dataType);
			res.append("name", name + "_2").append("id", id + "_2").append("value", dispValue);
			if(!StringUtils.isEmpty(col.getPromptTitle())) res.append("title", col.getPromptTitle());
			res.append(">");
			res.append("<img ").append("src",request().getContextPath()+"/images/datetime.gif").append("style", "cursor:hand").append("onclick","showDatetimeWindow('"+id+"_2','"+col.getDataType()+"');return false").append(">");
			res.append("</span>");
		} else if(propertyTypeInfo == PropertyTypeEnum.DOUBLE || propertyTypeInfo == PropertyTypeEnum.FLOAT || 
				propertyTypeInfo == PropertyTypeEnum.LONG || propertyTypeInfo == PropertyTypeEnum.INTEGER ) {
			String dispValue = values != null && values.length > 0?values[0]:"";
			res.append("<span nowrap>");
			res.append("<input type='text' class='commonInput' ").append("datatype", dataType);
			res.append("name", name + "_1").append("id", id + "_1").append("value", dispValue);
			if(!StringUtils.isEmpty(col.getPromptTitle())) res.append("title", col.getPromptTitle());
			res.append(">");
			res.append("</span>");
			
			res.append(" ~ ");
			dispValue = values != null && values.length > 1?values[1]:"";
			res.append("<span nowrap>");
			res.append("<input type='text' class='commonInput' ").append("datatype", dataType);
			res.append("name", name + "_2").append("id", id + "_2").append("value", dispValue);
			if(!StringUtils.isEmpty(col.getPromptTitle())) res.append("title", col.getPromptTitle());
			res.append(">");
			res.append("</span>");
		} else {
			String inputType = "text";
			String dispValue = values != null && values.length > 0?values[0]:"";
			if(StringUtils.equalsIgnoreCase(dispValue, "&nbsp;")){
				dispValue = "";
			}
			res.append("<span nowrap>");
			res.append("<input type='"+inputType+"' class='commonInput' ");
			res.append("name", name).append("id", id).append("value", dispValue);
			res.append("dataType", dataType);
			if(col.getMaxLength() > 0){
				res.append("maxlength", col.getMaxLength());
			}
			if(!StringUtils.isEmpty(col.getPromptTitle())) res.append("title", col.getPromptTitle());
			res.append(">");
			res.append("</span>");
		}
		return res.toString();
	}
	
	public void release(){
		super.release();
		this.paramEncoder = null;
		this.tableModel = null;
		this.boClassName = null;
		this.boMeta = null;
		this.view = null;
		this.uid = null;
		this.displayCellWidth = "";
		this.displayColumnCount = 0;
		this.visibleLevel = 0;
		this.cssClassSuffix = null;
		this.namePrefix = "";
	}
	
	class Obj extends AbstractEntity implements Entity {
		
	}
}
